#!/usr/bin/env python

# Copyright JS Foundation and other contributors, http://js.foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# This file is based on work under the following copyright and permission notice:
# https://github.com/test262-utils/test262-harness-py
# test262.py, _monkeyYaml.py, parseTestRecord.py

# license of test262.py:
# Copyright 2009 the Sputnik authors.  All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.
# This is derived from sputnik.py, the Sputnik console test runner,
# with elements from packager.py, which is separately
# copyrighted. TODO: Refactor so there is less duplication between
# test262.py and packager.py.

# license of _packager.py:
# Copyright (c) 2012 Ecma International.  All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.

# license of _monkeyYaml.py:
# Copyright 2014 by Sam Mikes.  All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.

# license of parseTestRecord.py:
# Copyright 2011 by Google, Inc.  All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.


from __future__ import print_function

import logging
import optparse
import os
from os import path
import platform
import re
import subprocess
import sys
import tempfile
import xml.dom.minidom
from collections import Counter

#######################################################################
# based on _monkeyYaml.py
#######################################################################

M_YAML_LIST_PATTERN = re.compile(r"^\[(.*)\]$")
M_YAML_MULTILINE_LIST = re.compile(r"^ *- (.*)$")


def yaml_load(string):
    return my_read_dict(string.splitlines())[1]


def my_read_dict(lines, indent=""):
    dictionary = {}
    key = None
    empty_lines = 0

    while lines:
        if not lines[0].startswith(indent):
            break

        line = lines.pop(0)
        if my_is_all_spaces(line):
            empty_lines += 1
            continue

        result = re.match(r"(.*?):(.*)", line)

        if result:
            if not dictionary:
                dictionary = {}
            key = result.group(1).strip()
            value = result.group(2).strip()
            (lines, value) = my_read_value(lines, value, indent)
            dictionary[key] = value
        else:
            if dictionary and key and key in dictionary:
                char = " " if empty_lines == 0 else "\n" * empty_lines
                dictionary[key] += char + line.strip()
            else:
                raise Exception("monkeyYaml is confused at " + line)
        empty_lines = 0

    if not dictionary:
        dictionary = None

    return lines, dictionary


def my_read_value(lines, value, indent):
    if value == ">" or value == "|":
        (lines, value) = my_multiline(lines, value == "|")
        value = value + "\n"
        return (lines, value)
    if lines and not value:
        if my_maybe_list(lines[0]):
            return my_multiline_list(lines, value)
        indent_match = re.match("(" + indent + r"\s+)", lines[0])
        if indent_match:
            if ":" in lines[0]:
                return my_read_dict(lines, indent_match.group(1))
            return my_multiline(lines, False)
    return lines, my_read_one_line(value)


def my_maybe_list(value):
    return M_YAML_MULTILINE_LIST.match(value)


def my_multiline_list(lines, value):
    # assume no explcit indentor (otherwise have to parse value)
    value = []
    indent = None
    while lines:
        line = lines.pop(0)
        leading = my_leading_spaces(line)
        if my_is_all_spaces(line):
            pass
        elif leading < indent:
            lines.insert(0, line)
            break
        else:
            indent = indent or leading
            value += [my_read_one_line(my_remove_list_header(indent, line))]
    return (lines, value)


def my_remove_list_header(indent, line):
    line = line[indent:]
    return M_YAML_MULTILINE_LIST.match(line).group(1)


def my_read_one_line(value):
    if M_YAML_LIST_PATTERN.match(value):
        return my_flow_list(value)
    elif re.match(r"^[-0-9]*$", value):
        try:
            value = int(value)
        except ValueError:
            pass
    elif re.match(r"^[-.0-9eE]*$", value):
        try:
            value = float(value)
        except ValueError:
            pass
    elif re.match(r"^('|\").*\1$", value):
        value = value[1:-1]
    return value


def my_flow_list(value):
    result = M_YAML_LIST_PATTERN.match(value)
    values = result.group(1).split(",")
    return [my_read_one_line(v.strip()) for v in values]


def my_multiline(lines, preserve_newlines=False):
    # assume no explcit indentor (otherwise have to parse value)
    value = ""
    indent = my_leading_spaces(lines[0])
    was_empty = None

    while lines:
        line = lines.pop(0)
        is_empty = my_is_all_spaces(line)

        if is_empty:
            if preserve_newlines:
                value += "\n"
        elif my_leading_spaces(line) < indent:
            lines.insert(0, line)
            break
        else:
            if preserve_newlines:
                if was_empty != None:
                    value += "\n"
            else:
                if was_empty:
                    value += "\n"
                elif was_empty is False:
                    value += " "
            value += line[(indent):]

        was_empty = is_empty

    return (lines, value)


def my_is_all_spaces(line):
    return len(line.strip()) == 0


def my_leading_spaces(line):
    return len(line) - len(line.lstrip(' '))


#######################################################################
# based on parseTestRecord.py
#######################################################################

# Matches trailing whitespace and any following blank lines.
_BLANK_LINES = r"([ \t]*[\r\n]{1,2})*"

# Matches the YAML frontmatter block.
# It must be non-greedy because test262-es2015/built-ins/Object/assign/Override.js contains a comment like yaml pattern
_YAML_PATTERN = re.compile(r"/\*---(.*?)---\*/" + _BLANK_LINES, re.DOTALL)

# Matches all known variants for the license block.
# https://github.com/tc39/test262/blob/705d78299cf786c84fa4df473eff98374de7135a/tools/lint/lib/checks/license.py
_LICENSE_PATTERN = re.compile(
    r'// Copyright( \([C]\))? (\w+) .+\. {1,2}All rights reserved\.[\r\n]{1,2}' +
    r'(' +
    r'// This code is governed by the( BSD)? license found in the LICENSE file\.' +
    r'|' +
    r'// See LICENSE for details.' +
    r'|' +
    r'// Use of this source code is governed by a BSD-style license that can be[\r\n]{1,2}' +
    r'// found in the LICENSE file\.' +
    r'|' +
    r'// See LICENSE or https://github\.com/tc39/test262/blob/master/LICENSE' +
    r')' + _BLANK_LINES, re.IGNORECASE)


def yaml_attr_parser(test_record, attrs, name, onerror=print):
    parsed = yaml_load(attrs)
    if parsed is None:
        onerror("Failed to parse yaml in name %s" % name)
        return

    for key in parsed:
        value = parsed[key]
        if key == "info":
            key = "commentary"
        test_record[key] = value

    if 'flags' in test_record:
        for flag in test_record['flags']:
            test_record[flag] = ""


def find_license(src):
    match = _LICENSE_PATTERN.search(src)
    if not match:
        return None

    return match.group(0)


def find_attrs(src):
    match = _YAML_PATTERN.search(src)
    if not match:
        return (None, None)

    return (match.group(0), match.group(1).strip())


def parse_test_record(src, name, onerror=print):
    # Find the license block.
    header = find_license(src)

    # Find the YAML frontmatter.
    (frontmatter, attrs) = find_attrs(src)

    # YAML frontmatter is required for all tests.
    if frontmatter is None:
        onerror("Missing frontmatter: %s" % name)

    # The license shuold be placed before the frontmatter and there shouldn't be
    # any extra content between the license and the frontmatter.
    if header is not None and frontmatter is not None:
        header_idx = src.index(header)
        frontmatter_idx = src.index(frontmatter)
        if header_idx > frontmatter_idx:
            onerror("Unexpected license after frontmatter: %s" % name)

        # Search for any extra test content, but ignore whitespace only or comment lines.
        extra = src[header_idx + len(header): frontmatter_idx]
        if extra and any(line.strip() and not line.lstrip().startswith("//") for line in extra.split("\n")):
            onerror(
                "Unexpected test content between license and frontmatter: %s" % name)

    # Remove the license and YAML parts from the actual test content.
    test = src
    if frontmatter is not None:
        test = test.replace(frontmatter, '')
    if header is not None:
        test = test.replace(header, '')

    test_record = {}
    test_record['header'] = header.strip() if header else ''
    test_record['test'] = test

    if attrs:
        yaml_attr_parser(test_record, attrs, name, onerror)

    # Report if the license block is missing in non-generated tests.
    if header is None and "generated" not in test_record and "hashbang" not in name:
        onerror("No license found in: %s" % name)

    return test_record


#######################################################################
# based on test262.py
#######################################################################

class Test262Error(Exception):
    def __init__(self, message):
        Exception.__init__(self)
        self.message = message


def report_error(error_string):
    raise Test262Error(error_string)


def build_options():
    result = optparse.OptionParser()
    result.add_option("--command", default=None,
                      help="The command-line to run")
    result.add_option("--tests", default=path.abspath('.'),
                      help="Path to the tests")
    result.add_option("--exclude-list", default=None,
                      help="Path to the excludelist.xml file")
    result.add_option("--cat", default=False, action="store_true",
                      help="Print packaged test code that would be run")
    result.add_option("--summary", default=False, action="store_true",
                      help="Print summary after running tests")
    result.add_option("--full-summary", default=False, action="store_true",
                      help="Print summary and test output after running tests")
    result.add_option("--strict_only", default=False, action="store_true",
                      help="Test only strict mode")
    result.add_option("--non_strict_only", default=False, action="store_true",
                      help="Test only non-strict mode")
    result.add_option("--unmarked_default", default="both",
                      help="default mode for tests of unspecified strictness")
    result.add_option("--logname", help="Filename to save stdout to")
    result.add_option("--loglevel", default="warning",
                      help="sets log level to debug, info, warning, error, or critical")
    result.add_option("--print-handle", default="print",
                      help="Command to print from console")
    result.add_option("--list-includes", default=False, action="store_true",
                      help="List includes required by tests")
    return result


def validate_options(options):
    if not options.command:
        report_error("A --command must be specified.")
    if not path.exists(options.tests):
        report_error("Couldn't find test path '%s'" % options.tests)


def is_windows():
    actual_platform = platform.system()
    return (actual_platform == 'Windows') or (actual_platform == 'Microsoft')


class TempFile(object):

    def __init__(self, suffix="", prefix="tmp", text=False):
        self.suffix = suffix
        self.prefix = prefix
        self.text = text
        self.file_desc = None
        self.name = None
        self.is_closed = False
        self.open_file()

    def open_file(self):
        (self.file_desc, self.name) = tempfile.mkstemp(
            suffix=self.suffix,
            prefix=self.prefix,
            text=self.text)

    def write(self, string):
        os.write(self.file_desc, string)

    def read(self):
        file_desc = file(self.name)
        result = file_desc.read()
        file_desc.close()
        return result

    def close(self):
        if not self.is_closed:
            self.is_closed = True
            os.close(self.file_desc)

    def dispose(self):
        try:
            self.close()
            os.unlink(self.name)
        except OSError as exception:
            logging.error("Error disposing temp file: %s", str(exception))


class TestResult(object):

    def __init__(self, exit_code, stdout, stderr, case):
        self.exit_code = exit_code
        self.stdout = stdout
        self.stderr = stderr
        self.case = case

    def report_outcome(self, long_format):
        name = self.case.get_name()
        mode = self.case.get_mode()
        if self.has_unexpected_outcome():
            if self.case.is_negative():
                print("=== %s passed in %s, but was expected to fail ===" % (name, mode))
                print("--- expected error: %s ---\n" % self.case.get_negative_type())
            else:
                if long_format:
                    print("=== %s failed in %s ===" % (name, mode))
                else:
                    print("%s in %s: " % (name, mode))
            self.write_output(sys.stdout)
            if long_format:
                print("===")
        elif self.case.is_negative():
            print("%s failed in %s as expected" % (name, mode))
        else:
            print("%s passed in %s" % (name, mode))

    def write_output(self, target):
        out = self.stdout.strip()
        if out:
            target.write("--- output --- \n %s" % out)
        error = self.stderr.strip()
        if error:
            target.write("--- errors ---  \n %s" % error)

        target.write("\n--- exit code: %d ---\n" % self.exit_code)

    def has_failed(self):
        return self.exit_code != 0

    def async_has_failed(self):
        return 'Test262:AsyncTestComplete' not in self.stdout

    def has_unexpected_outcome(self):
        if self.case.is_async_test():
            return self.async_has_failed() or self.has_failed()
        elif self.case.is_negative():
            return not (self.has_failed() and self.case.negative_match(self.get_error_output()))

        return self.has_failed()

    def get_error_output(self):
        if self.stderr:
            return self.stderr
        return self.stdout


class TestCase(object):

    def __init__(self, suite, name, full_path, strict_mode):
        self.suite = suite
        self.name = name
        self.full_path = full_path
        self.strict_mode = strict_mode
        with open(self.full_path) as file_desc:
            self.contents = file_desc.read()
        test_record = parse_test_record(self.contents, name)
        self.test = test_record["test"]
        del test_record["test"]
        del test_record["header"]
        test_record.pop("commentary", None)    # do not throw if missing
        self.test_record = test_record

        self.validate()

    def negative_match(self, stderr):
        neg = re.compile(self.get_negative_type())
        return re.search(neg, stderr)

    def get_negative(self):
        if not self.is_negative():
            return None
        return self.test_record["negative"]

    def get_negative_type(self):
        negative = self.get_negative()
        if isinstance(negative, dict) and "type" in negative:
            return negative["type"]
        return negative

    def get_negative_phase(self):
        negative = self.get_negative()
        return negative and "phase" in negative and negative["phase"]

    def get_name(self):
        return path.join(*self.name)

    def get_mode(self):
        if self.strict_mode:
            return "strict mode"
        return "non-strict mode"

    def get_path(self):
        return self.name

    def is_negative(self):
        return 'negative' in self.test_record

    def is_only_strict(self):
        return 'onlyStrict' in self.test_record

    def is_no_strict(self):
        return 'noStrict' in self.test_record or self.is_raw()

    def is_raw(self):
        return 'raw' in self.test_record

    def is_async_test(self):
        return 'async' in self.test_record or '$DONE' in self.test

    def get_include_list(self):
        if self.test_record.get('includes'):
            return self.test_record['includes']
        return []

    def get_additional_includes(self):
        return '\n'.join([self.suite.get_include(include) for include in self.get_include_list()])

    def get_source(self):
        if self.is_raw():
            return self.test

        source = self.suite.get_include("sta.js") + \
            self.suite.get_include("assert.js")

        if self.is_async_test():
            source = source + \
                self.suite.get_include("timer.js") + \
                self.suite.get_include("doneprintHandle.js").replace(
                    'print', self.suite.print_handle)

        source = source + \
            self.get_additional_includes() + \
            self.test + '\n'

        if self.get_negative_phase() == "early":
            source = ("throw 'Expected an early error, but code was executed.';\n" +
                      source)

        if self.strict_mode:
            source = '"use strict";\nvar strict_mode = true;\n' + source
        else:
            # add comment line so line numbers match in both strict and non-strict version
            source = '//"no strict";\nvar strict_mode = false;\n' + source

        return source

    @staticmethod
    def instantiate_template(template, params):
        def get_parameter(match):
            key = match.group(1)
            return params.get(key, match.group(0))

        return re.sub(r"\{\{(\w+)\}\}", get_parameter, template)

    @staticmethod
    def execute(command):
        if is_windows():
            args = '%s' % command
        else:
            args = command.split(" ")
        stdout = TempFile(prefix="test262-out-")
        stderr = TempFile(prefix="test262-err-")
        try:
            logging.info("exec: %s", str(args))
            process = subprocess.Popen(
                args,
                shell=is_windows(),
                stdout=stdout.file_desc,
                stderr=stderr.file_desc
            )
            code = process.wait()
            out = stdout.read()
            err = stderr.read()
        finally:
            stdout.dispose()
            stderr.dispose()
        return (code, out, err)

    def run_test_in(self, command_template, tmp):
        tmp.write(self.get_source())
        tmp.close()
        command = TestCase.instantiate_template(command_template, {
            'path': tmp.name
        })
        (code, out, err) = TestCase.execute(command)
        return TestResult(code, out, err, self)

    def run(self, command_template):
        tmp = TempFile(suffix=".js", prefix="test262-", text=True)
        try:
            result = self.run_test_in(command_template, tmp)
        finally:
            tmp.dispose()
        return result

    def print_source(self):
        print(self.get_source())

    def validate(self):
        flags = self.test_record.get("flags")
        phase = self.get_negative_phase()

        if phase not in [None, False, "parse", "early", "runtime", "resolution"]:
            raise TypeError("Invalid value for negative phase: " + phase)

        if not flags:
            return

        if 'raw' in flags:
            if 'noStrict' in flags:
                raise TypeError("The `raw` flag implies the `noStrict` flag")
            elif 'onlyStrict' in flags:
                raise TypeError(
                    "The `raw` flag is incompatible with the `onlyStrict` flag")
            elif self.get_include_list():
                raise TypeError(
                    "The `raw` flag is incompatible with the `includes` tag")


class ProgressIndicator(object):

    def __init__(self, count):
        self.count = count
        self.succeeded = 0
        self.failed = 0
        self.failed_tests = []

    def has_run(self, result):
        result.report_outcome(True)
        if result.has_unexpected_outcome():
            self.failed += 1
            self.failed_tests.append(result)
        else:
            self.succeeded += 1


def make_plural(num):
    if num == 1:
        return (num, "")
    return (num, "s")


def percent_format(partial, total):
    return "%i test%s (%.1f%%)" % (make_plural(partial) +
                                   ((100.0 * partial)/total,))


class TestSuite(object):

    def __init__(self, root, strict_only, non_strict_only, unmarked_default, print_handle, exclude_list_path):
        self.test_root = path.join(root, 'test')
        self.lib_root = path.join(root, 'harness')
        self.strict_only = strict_only
        self.non_strict_only = non_strict_only
        self.unmarked_default = unmarked_default
        self.print_handle = print_handle
        self.include_cache = {}
        self.exclude_list = []
        self.logf = None

        if exclude_list_path:
            if os.path.exists(exclude_list_path):
                self.exclude_list = xml.dom.minidom.parse(exclude_list_path)
                self.exclude_list = self.exclude_list.getElementsByTagName("test")
                self.exclude_list = [x.getAttribute("id") for x in self.exclude_list]
            else:
                report_error("Couldn't find excludelist '%s'" % exclude_list_path)

    def validate(self):
        if not path.exists(self.test_root):
            report_error("No test repository found")
        if not path.exists(self.lib_root):
            report_error("No test library found")

    @staticmethod
    def is_hidden(test_path):
        return test_path.startswith('.') or test_path == 'CVS'

    @staticmethod
    def is_test_case(test_path):
        return test_path.endswith('.js') and not test_path.endswith('_FIXTURE.js')

    @staticmethod
    def should_run(rel_path, tests):
        if not tests:
            return True
        for test in tests:
            if test in rel_path:
                return True
        return False

    def get_include(self, name):
        if not name in self.include_cache:
            static = path.join(self.lib_root, name)
            if path.exists(static):
                with open(static) as file_desc:
                    contents = file_desc.read()
                    contents = re.sub(r'\r\n', '\n', contents)
                    self.include_cache[name] = contents + "\n"
            else:
                report_error("Can't find: " + static)
        return self.include_cache[name]

    def enumerate_tests(self, tests):
        logging.info("Listing tests in %s", self.test_root)
        cases = []
        for root, dirs, files in os.walk(self.test_root):
            for hidden_dir in [x for x in dirs if self.is_hidden(x)]:
                dirs.remove(hidden_dir)
            dirs.sort()
            for test_path in filter(TestSuite.is_test_case, sorted(files)):
                full_path = path.join(root, test_path)
                if full_path.startswith(self.test_root):
                    rel_path = full_path[len(self.test_root)+1:]
                else:
                    logging.warning("Unexpected path %s", full_path)
                    rel_path = full_path
                if self.should_run(rel_path, tests):
                    basename = path.basename(full_path)[:-3]
                    name = rel_path.split(path.sep)[:-1] + [basename]
                    if rel_path in self.exclude_list:
                        print('Excluded: ' + rel_path)
                    else:
                        if not self.non_strict_only:
                            strict_case = TestCase(self, name, full_path, True)
                            if not strict_case.is_no_strict():
                                if strict_case.is_only_strict() or self.unmarked_default in ['both', 'strict']:
                                    cases.append(strict_case)
                        if not self.strict_only:
                            non_strict_case = TestCase(self, name, full_path, False)
                            if not non_strict_case.is_only_strict():
                                if non_strict_case.is_no_strict() or self.unmarked_default in ['both', 'non_strict']:
                                    cases.append(non_strict_case)
        logging.info("Done listing tests")
        return cases

    def print_summary(self, progress, logfile):

        def write(string):
            if logfile:
                self.logf.write(string + "\n")
            print(string)

        print("")
        write("=== Summary ===")
        count = progress.count
        succeeded = progress.succeeded
        failed = progress.failed
        write(" - Ran %i test%s" % make_plural(count))
        if progress.failed == 0:
            write(" - All tests succeeded")
        else:
            write(" - Passed " + percent_format(succeeded, count))
            write(" - Failed " + percent_format(failed, count))
            positive = [c for c in progress.failed_tests if not c.case.is_negative()]
            negative = [c for c in progress.failed_tests if c.case.is_negative()]
            if positive:
                print("")
                write("Failed Tests")
                for result in positive:
                    write("  %s in %s" % (result.case.get_name(), result.case.get_mode()))
            if negative:
                print("")
                write("Expected to fail but passed ---")
                for result in negative:
                    write("  %s in %s" % (result.case.get_name(), result.case.get_mode()))

    def print_failure_output(self, progress, logfile):
        for result in progress.failed_tests:
            if logfile:
                self.write_log(result)
            print("")
            result.report_outcome(False)

    def run(self, command_template, tests, print_summary, full_summary, logname):
        if not "{{path}}" in command_template:
            command_template += " {{path}}"
        cases = self.enumerate_tests(tests)
        if not cases:
            report_error("No tests to run")
        progress = ProgressIndicator(len(cases))
        if logname:
            self.logf = open(logname, "w")

        for case in cases:
            result = case.run(command_template)
            if logname:
                self.write_log(result)
            progress.has_run(result)

        if print_summary:
            self.print_summary(progress, logname)
            if full_summary:
                self.print_failure_output(progress, logname)
            else:
                print("")
                print("Use --full-summary to see output from failed tests")
        print("")
        return progress.failed

    def write_log(self, result):
        name = result.case.get_name()
        mode = result.case.get_mode()
        if result.has_unexpected_outcome():
            if result.case.is_negative():
                self.logf.write(
                    "=== %s passed in %s, but was expected to fail === \n" % (name, mode))
                self.logf.write("--- expected error: %s ---\n" % result.case.GetNegativeType())
                result.write_output(self.logf)
            else:
                self.logf.write("=== %s failed in %s === \n" % (name, mode))
                result.write_output(self.logf)
            self.logf.write("===\n")
        elif result.case.is_negative():
            self.logf.write("%s failed in %s as expected \n" % (name, mode))
        else:
            self.logf.write("%s passed in %s \n" % (name, mode))

    def print_source(self, tests):
        cases = self.enumerate_tests(tests)
        if cases:
            cases[0].print_source()

    def list_includes(self, tests):
        cases = self.enumerate_tests(tests)
        includes_dict = Counter()
        for case in cases:
            includes = case.get_include_list()
            includes_dict.update(includes)

        print(includes_dict)


def main():
    code = 0
    parser = build_options()
    (options, args) = parser.parse_args()
    validate_options(options)

    test_suite = TestSuite(options.tests,
                           options.strict_only,
                           options.non_strict_only,
                           options.unmarked_default,
                           options.print_handle,
                           options.exclude_list)

    test_suite.validate()
    if options.loglevel == 'debug':
        logging.basicConfig(level=logging.DEBUG)
    elif options.loglevel == 'info':
        logging.basicConfig(level=logging.INFO)
    elif options.loglevel == 'warning':
        logging.basicConfig(level=logging.WARNING)
    elif options.loglevel == 'error':
        logging.basicConfig(level=logging.ERROR)
    elif options.loglevel == 'critical':
        logging.basicConfig(level=logging.CRITICAL)

    if options.cat:
        test_suite.print_source(args)
    elif options.list_includes:
        test_suite.list_includes(args)
    else:
        code = test_suite.run(options.command, args,
                              options.summary or options.full_summary,
                              options.full_summary,
                              options.logname)
    return code


if __name__ == '__main__':
    try:
        sys.exit(main())
    except Test262Error as exception:
        print("Error: %s" % exception.message)
        sys.exit(1)
